#ifndef TOKEN_H__
#define TOKEN_H__

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if 0
import std.io;
import std.lib;
import ..util.string;
#endif

enum TokenTag
{
	CHARACTER_LITERAL_TOKEN,
	INTEGER_LITERAL_TOKEN,
	NAME_TOKEN,
	STRING_LITERAL_TOKEN,

	KEYWORD_BOOLEAN_TOKEN,
	KEYWORD_BREAK_TOKEN,
	KEYWORD_BYTE_TOKEN,
	KEYWORD_CASE_TOKEN,
	KEYWORD_CATCH_TOKEN,
	KEYWORD_CHAR_TOKEN,
	KEYWORD_CLASS_TOKEN,
	KEYWORD_CONST_TOKEN,
	KEYWORD_CONTINUE_TOKEN,
	KEYWORD_DEFAULT_TOKEN,
	KEYWORD_DO_TOKEN,
	KEYWORD_DOUBLE_TOKEN,
	KEYWORD_ELIF_TOKEN,
	KEYWORD_ELSE_TOKEN,
	KEYWORD_ENUM_TOKEN,
	KEYWORD_EXTERN_TOKEN,
	KEYWORD_FINALLY_TOKEN,
	KEYWORD_FLOAT_TOKEN,
	KEYWORD_FOR_TOKEN,
	KEYWORD_FOREACH_TOKEN,
	KEYWORD_FUNCTION_TOKEN,
	KEYWORD_GOTO_TOKEN,
	KEYWORD_HIDDEN_TOKEN,
	KEYWORD_IF_TOKEN,
	KEYWORD_IMPORT_TOKEN,
	KEYWORD_IN_TOKEN,
	KEYWORD_INLINE_TOKEN,
	KEYWORD_INT_TOKEN,
	KEYWORD_LAMBDA_TOKEN,
	KEYWORD_LONG_TOKEN,
	KEYWORD_NULL_TOKEN,
	KEYWORD_RETURN_TOKEN,
	KEYWORD_SHORT_TOKEN,
	KEYWORD_SIGNED_TOKEN,
	KEYWORD_SIZEOF_TOKEN,
	KEYWORD_STATIC_TOKEN,
	KEYWORD_STRUCT_TOKEN,
	KEYWORD_SYNCHRONIZED_TOKEN,
	KEYWORD_TEMPLATE_TOKEN,
	KEYWORD_THIS_TOKEN,
	KEYWORD_THROW_TOKEN,
	KEYWORD_TRANSFORM_TOKEN,
	KEYWORD_TYPEDEF_TOKEN,
	KEYWORD_UNION_TOKEN,
	KEYWORD_UNSIGNED_TOKEN,
	KEYWORD_VOID_TOKEN,
	KEYWORD_VOLATILE_TOKEN,
	KEYWORD_WHERE_TOKEN,
	KEYWORD_WHILE_TOKEN,

	AMPERSAND,
	AMPERSAND_AMPERSAND,
	AMPERSAND_EQUALS,
	AT,
	BAR,
	BAR_BAR,
	BAR_EQUALS,
	CARAT,
	CARAT_EQUALS,
	CLOSE_BRACE,
	CLOSE_BRACKET,
	CLOSE_PARENTHESE,
	COLON,
	COMMA,
	DASH,
	DASH_DASH,
	DASH_EQUALS,
	DASH_GREATER_THAN,
	EQUALS,
	EQUALS_EQUALS,
	EXCLAMATION_POINT,
	EXCLAMATION_POINT_EQUALS,
	GREATER_THAN,
	GREATER_THAN_EQUALS,
	GREATER_THAN_GREATER_THAN,
	GREATER_THAN_GREATER_THAN_EQUALS,
	LESS_THAN,
	LESS_THAN_EQUALS,
	LESS_THAN_LESS_THAN,
	LESS_THAN_LESS_THAN_EQUALS,
	OPEN_BRACE,
	OPEN_BRACKET,
	OPEN_PARENTHESE,
	PERCENT,
	PERCENT_EQUALS,
	PERIOD,
	PLUS,
	PLUS_EQUALS,
	PLUS_PLUS,
	QUESTION_MARK,
	SEMICOLON,
	SLASH,
	SLASH_EQUALS,
	STAR,
	STAR_EQUALS,
	TILDE,
	TILDE_EQUALS
};

char* tokenTagToString(enum TokenTag tag)
{
	switch(tag)
	{
		case CHARACTER_LITERAL_TOKEN: return "CHARACTER_LITERAL_TOKEN";
		case INTEGER_LITERAL_TOKEN: return "INTEGER_LITERAL_TOKEN";
		case NAME_TOKEN: return "NAME_TOKEN";
		case STRING_LITERAL_TOKEN: return "STRING_LITERAL_TOKEN";
		case KEYWORD_BOOLEAN_TOKEN: return "KEYWORD_BOOLEAN_TOKEN";
		case KEYWORD_BREAK_TOKEN: return "KEYWORD_BREAK_TOKEN";
		case KEYWORD_BYTE_TOKEN: return "KEYWORD_BYTE_TOKEN";
		case KEYWORD_CASE_TOKEN: return "KEYWORD_CASE_TOKEN";
		case KEYWORD_CATCH_TOKEN: return "KEYWORD_CATCH_TOKEN";
		case KEYWORD_CHAR_TOKEN: return "KEYWORD_CHAR_TOKEN";
		case KEYWORD_CLASS_TOKEN: return "KEYWORD_CLASS_TOKEN";
		case KEYWORD_CONST_TOKEN: return "KEYWORD_CONST_TOKEN";
		case KEYWORD_CONTINUE_TOKEN: return "KEYWORD_CONTINUE_TOKEN";
		case KEYWORD_DEFAULT_TOKEN: return "KEYWORD_DEFAULT_TOKEN";
		case KEYWORD_DO_TOKEN: return "KEYWORD_DO_TOKEN";
		case KEYWORD_DOUBLE_TOKEN: return "KEYWORD_DOUBLE_TOKEN";
		case KEYWORD_ELIF_TOKEN: return "KEYWORD_ELIF_TOKEN";
		case KEYWORD_ELSE_TOKEN: return "KEYWORD_ELSE_TOKEN";
		case KEYWORD_ENUM_TOKEN: return "KEYWORD_ENUM_TOKEN";
		case KEYWORD_EXTERN_TOKEN: return "KEYWORD_EXTERN_TOKEN";
		case KEYWORD_FINALLY_TOKEN: return "KEYWORD_FINALLY_TOKEN";
		case KEYWORD_FLOAT_TOKEN: return "KEYWORD_FLOAT_TOKEN";
		case KEYWORD_FOR_TOKEN: return "KEYWORD_FOR_TOKEN";
		case KEYWORD_FOREACH_TOKEN: return "KEYWORD_FOREACH_TOKEN";
		case KEYWORD_FUNCTION_TOKEN: return "KEYWORD_FUNCTION_TOKEN";
		case KEYWORD_GOTO_TOKEN: return "KEYWORD_GOTO_TOKEN";
		case KEYWORD_HIDDEN_TOKEN: return "KEYWORD_HIDDEN_TOKEN";
		case KEYWORD_IF_TOKEN: return "KEYWORD_IF_TOKEN";
		case KEYWORD_IMPORT_TOKEN: return "KEYWORD_IMPORT_TOKEN";
		case KEYWORD_IN_TOKEN: return "KEYWORD_IN_TOKEN";
		case KEYWORD_INLINE_TOKEN: return "KEYWORD_INLINE_TOKEN";
		case KEYWORD_INT_TOKEN: return "KEYWORD_INT_TOKEN";
		case KEYWORD_LAMBDA_TOKEN: return "KEYWORD_LAMBDA_TOKEN";
		case KEYWORD_LONG_TOKEN: return "KEYWORD_LONG_TOKEN";
		case KEYWORD_NULL_TOKEN: return "KEYWORD_NULL_TOKEN";
		case KEYWORD_RETURN_TOKEN: return "KEYWORD_RETURN_TOKEN";
		case KEYWORD_SHORT_TOKEN: return "KEYWORD_SHORT_TOKEN";
		case KEYWORD_SIGNED_TOKEN: return "KEYWORD_SIGNED_TOKEN";
		case KEYWORD_SIZEOF_TOKEN: return "KEYWORD_SIZEOF_TOKEN";
		case KEYWORD_STATIC_TOKEN: return "KEYWORD_STATIC_TOKEN";
		case KEYWORD_STRUCT_TOKEN: return "KEYWORD_STRUCT_TOKEN";
		case KEYWORD_SYNCHRONIZED_TOKEN: return "KEYWORD_SYNCHRONIZED_TOKEN";
		case KEYWORD_TEMPLATE_TOKEN: return "KEYWORD_TEMPLATE_TOKEN";
		case KEYWORD_THIS_TOKEN: return "KEYWORD_THIS_TOKEN";
		case KEYWORD_THROW_TOKEN: return "KEYWORD_THROW_TOKEN";
		case KEYWORD_TRANSFORM_TOKEN: return "KEYWORD_TRANSFORM_TOKEN";
		case KEYWORD_TYPEDEF_TOKEN: return "KEYWORD_TYPEDEF_TOKEN";
		case KEYWORD_UNION_TOKEN: return "KEYWORD_UNION_TOKEN";
		case KEYWORD_UNSIGNED_TOKEN: return "KEYWORD_UNSIGNED_TOKEN";
		case KEYWORD_VOID_TOKEN: return "KEYWORD_VOID_TOKEN";
		case KEYWORD_VOLATILE_TOKEN: return "KEYWORD_VOLATILE_TOKEN";
		case KEYWORD_WHERE_TOKEN: return "KEYWORD_WHERE_TOKEN";
		case KEYWORD_WHILE_TOKEN: return "KEYWORD_WHILE_TOKEN";
		case AMPERSAND: return "AMPERSAND";
		case AMPERSAND_AMPERSAND: return "AMPERSAND_AMPERSAND";
		case AMPERSAND_EQUALS: return "AMPERSAND_EQUALS";
		case AT: return "AT";
		case BAR: return "BAR";
		case BAR_BAR: return "BAR_BAR";
		case BAR_EQUALS: return "BAR_EQUALS";
		case CARAT: return "CARAT";
		case CARAT_EQUALS: return "CARAT_EQUALS";
		case CLOSE_BRACE: return "CLOSE_BRACE";
		case CLOSE_BRACKET: return "CLOSE_BRACKET";
		case CLOSE_PARENTHESE: return "CLOSE_PARENTHESE";
		case COLON: return "COLON";
		case COMMA: return "COMMA";
		case DASH: return "DASH";
		case DASH_DASH: return "DASH_DASH";
		case DASH_EQUALS: return "DASH_EQUALS";
		case DASH_GREATER_THAN: return "DASH_GREATER_THAN";
		case EQUALS: return "EQUALS";
		case EQUALS_EQUALS: return "EQUALS_EQUALS";
		case EXCLAMATION_POINT: return "EXCLAMATION_POINT";
		case EXCLAMATION_POINT_EQUALS: return "EXCLAMATION_POINT_EQUALS";
		case GREATER_THAN: return "GREATER_THAN";
		case GREATER_THAN_EQUALS: return "GREATER_THAN_EQUALS";
		case GREATER_THAN_GREATER_THAN: return "GREATER_THAN_GREATER_THAN";
		case GREATER_THAN_GREATER_THAN_EQUALS:
			return "GREATER_THAN_GREATER_THAN_EQUALS";
		case LESS_THAN: return "LESS_THAN";
		case LESS_THAN_EQUALS: return "LESS_THAN_EQUALS";
		case LESS_THAN_LESS_THAN: return "LESS_THAN_LESS_THAN";
		case LESS_THAN_LESS_THAN_EQUALS: return "LESS_THAN_LESS_THAN_EQUALS";
		case OPEN_BRACE: return "OPEN_BRACE";
		case OPEN_BRACKET: return "OPEN_BRACKET";
		case OPEN_PARENTHESE: return "OPEN_PARENTHESE";
		case PERCENT: return "PERCENT";
		case PERCENT_EQUALS: return "PERCENT_EQUALS";
		case PERIOD: return "PERIOD";
		case PLUS: return "PLUS";
		case PLUS_EQUALS: return "PLUS_EQUALS";
		case PLUS_PLUS: return "PLUS_PLUS";
		case QUESTION_MARK: return "QUESTION_MARK";
		case SEMICOLON: return "SEMICOLON";
		case SLASH: return "SLASH";
		case SLASH_EQUALS: return "SLASH_EQUALS";
		case STAR: return "STAR";
		case STAR_EQUALS: return "STAR_EQUALS";
		case TILDE: return "TILDE";
		case TILDE_EQUALS: return "TILDE_EQUALS";
	}

	return NULL;
}

struct Token { enum TokenTag tag; char* characters; int line; };

struct Token* allocateToken(enum TokenTag tag, char* characters, int line)
{
	assert(line > 0);

	struct Token* r = malloc(sizeof(struct Token));
	r->tag = tag;
	r->characters = characters;
	r->line = line;
	return r;
}

void freeToken(struct Token* f)
{
	if(f == NULL) return;

	if(f->characters != NULL)
		free(f->characters);

	free(f);
}

struct TokenList { struct Token* head; struct TokenList* tail; };

struct TokenList* allocateTokenList(struct Token* head, struct TokenList* tail)
{
	assert(head != NULL);

	struct TokenList* r = malloc(sizeof(struct TokenList));
	r->head = head;
	r->tail = tail;
	return r;
}

void freeTokenList(struct TokenList* tokenList)
{
	if(tokenList == NULL)
		return;

	free(tokenList->head);
	freeTokenList(tokenList->tail);
	free(tokenList);
}

int tokenListLength(struct TokenList* tokenList)
{
	if(tokenList == NULL)
		return 0;

	return tokenListLength(tokenList->tail) + 1;
}

void blockComment(char** source,int* line)
{
	int depth = 0;

	do switch(**source)
	{
		case '\0':
		case EOF:
			return;

		case '/':
			if((*source)[1] == '*')
			{
				depth++;
				(*source) += 2;
			}

			else (*source)++;

			break;

		case '*':
			if((*source)[1] == '/')
			{
				depth--;
				(*source) += 2;
			}

			else (*source)++;

			break;

		case '\n':
			(*line)++;
			// fall through intentional

		default:
			(*source)++;

	} while(depth != 0);
}

// TODO Add support for comments.
void ignore(char** source,int* line)
{
	#if DEBUG
	printf("ignore()\n");
	#endif

	switch(**source)
	{
		case '\0':
		case EOF:
			return;

		case '\n':
			(*line)++;
			// fall through intentional
		case ' ':
		case '\t':
		case '\r':
			(*source)++;
			ignore(source,line);
			return;

		case '/':
			switch((*source)[1])
			{
				case '/':
					while(**source != '\n') (*source)++;
					ignore(source,line);
					return;

				case '*':
					blockComment(source,line);
					ignore(source,line);
					return;
			}
	}
}

struct Token* matchIntegerLiteral(char** source, int* line)
{
	#if DEBUG
	printf("matchIntegerLiteral()\n");
	#endif

	char* start = *source;

	while('0' <= **source && **source <= '9')
		(*source)++;

	if(*source > start)
	{
		char* r = malloc(*source - start + 1);
		memcpy(r,start,*source - start);
		r[*source - start] = '\0';

		return allocateToken(INTEGER_LITERAL_TOKEN,r,*line);
	}

	return NULL;
}

struct Token* matchKeyword(char* name, int* line)
{
	#if DEBUG
	printf("matchString()\n");
	#endif

	struct { char* characters; enum TokenTag tag; } pair[] =
	{
		{"boolean",KEYWORD_BOOLEAN_TOKEN},
		{"break",KEYWORD_BREAK_TOKEN},
		{"byte",KEYWORD_BYTE_TOKEN},
		{"case",KEYWORD_CASE_TOKEN},
		{"catch",KEYWORD_CATCH_TOKEN},
		{"char",KEYWORD_CHAR_TOKEN},
		{"class",KEYWORD_CLASS_TOKEN},
		{"const",KEYWORD_CONST_TOKEN},
		{"continue",KEYWORD_CONTINUE_TOKEN},
		{"default",KEYWORD_DEFAULT_TOKEN},
		{"do",KEYWORD_DO_TOKEN},
		{"double",KEYWORD_DOUBLE_TOKEN},
		{"elif",KEYWORD_ELIF_TOKEN},
		{"else",KEYWORD_ELSE_TOKEN},
		{"enum",KEYWORD_ENUM_TOKEN},
		{"finally",KEYWORD_FINALLY_TOKEN},
		{"float",KEYWORD_FLOAT_TOKEN},
		{"for",KEYWORD_FOR_TOKEN},
		{"foreach",KEYWORD_FOREACH_TOKEN},
		{"function",KEYWORD_FUNCTION_TOKEN},
		{"goto",KEYWORD_GOTO_TOKEN},
		{"if",KEYWORD_IF_TOKEN},
		{"import",KEYWORD_IMPORT_TOKEN},
		{"in",KEYWORD_IN_TOKEN},
		{"inline",KEYWORD_INLINE_TOKEN},
		{"int",KEYWORD_INT_TOKEN},
		{"lambda",KEYWORD_LAMBDA_TOKEN},
		{"long",KEYWORD_LONG_TOKEN},
		{"null",KEYWORD_NULL_TOKEN},
		{"NULL",KEYWORD_NULL_TOKEN},
		{"return",KEYWORD_RETURN_TOKEN},
		{"short",KEYWORD_SHORT_TOKEN},
		{"signed",KEYWORD_SIGNED_TOKEN},
		{"sizeof",KEYWORD_SIZEOF_TOKEN},
		{"static",KEYWORD_STATIC_TOKEN},
		{"struct",KEYWORD_STRUCT_TOKEN},
		{"synchronized",KEYWORD_SYNCHRONIZED_TOKEN},
		{"template",KEYWORD_TEMPLATE_TOKEN},
		{"this",KEYWORD_THIS_TOKEN},
		{"throw",KEYWORD_THROW_TOKEN},
		{"transform",KEYWORD_TRANSFORM_TOKEN},
		{"typedef",KEYWORD_TYPEDEF_TOKEN},
		{"union",KEYWORD_UNION_TOKEN},
		{"unsigned",KEYWORD_UNSIGNED_TOKEN},
		{"void",KEYWORD_VOID_TOKEN},
		{"volatile",KEYWORD_VOLATILE_TOKEN},
		{"where",KEYWORD_WHERE_TOKEN},
		{"while",KEYWORD_WHILE_TOKEN}
	};

	int i;
	for(i = 0; i < 48; i++)
		if(strcmp(pair[i].characters,name) == 0)
			return allocateToken(pair[i].tag,NULL,*line);

	return NULL;
}

struct Token* matchOperator(char** source, int* line)
{
	struct { char* characters; enum TokenTag tag } pair[] =
	{
		{"&&",AMPERSAND_AMPERSAND},
		{"&=",AMPERSAND_EQUALS},
		{"&",AMPERSAND},
		{"@",AT},
		{"||",BAR_BAR},
		{"|=",BAR_EQUALS},
		{"|",BAR},
		{"^=",CARAT_EQUALS},
		{"^",CARAT},
		{"}",CLOSE_BRACE},
		{"]",CLOSE_BRACKET},
		{")",CLOSE_PARENTHESE},
		{":",COLON},
		{",",COMMA},
		{"--",DASH_DASH},
		{"-=",DASH_EQUALS},
		{"->",DASH_GREATER_THAN},
		{"-",DASH},
		{"==",EQUALS_EQUALS},
		{"=",EQUALS},
		{"!=",EXCLAMATION_POINT_EQUALS},
		{"!",EXCLAMATION_POINT},
		{">=",GREATER_THAN_EQUALS},
		{">>=",GREATER_THAN_GREATER_THAN_EQUALS},
		{">>",GREATER_THAN_GREATER_THAN},
		{">",GREATER_THAN},
		{"<=",LESS_THAN_EQUALS},
		{"<<=",LESS_THAN_LESS_THAN_EQUALS},
		{"<<",LESS_THAN_LESS_THAN},
		{"<",LESS_THAN},
		{"{",OPEN_BRACE},
		{"[",OPEN_BRACKET},
		{"(",OPEN_PARENTHESE},
		{"%=",PERCENT_EQUALS},
		{"%",PERCENT},
		{".",PERIOD},
		{"+=",PLUS_EQUALS},
		{"++",PLUS_PLUS},
		{"+",PLUS},
		{"?",QUESTION_MARK},
		{";",SEMICOLON},
		{"/=",SLASH_EQUALS},
		{"/",SLASH},
		{"*=",STAR_EQUALS},
		{"*",STAR},
		{"~=",TILDE_EQUALS},
		{"~",TILDE}
	};

	char* k;
	char* s;

	int i;
	for(i = 0; i < 47; i++)
	{
		k = pair[i].characters;
		s = *source;

		while(*k != '\0' && *k == *s) { k++; s++; }

		if(*k == '\0')
		{
			*source = s;
			return allocateToken(pair[i].tag,NULL,*line);
		}
	}

	return NULL;
}

struct Token* matchName(char** source, int* line)
{
	#if DEBUG
	printf("matchName()\n");
	#endif

	char* start = *source;

	while(('A' <= **source && **source <= 'Z')
			|| ('a' <= **source && **source <= 'z'))
		(*source)++;

	if(*source > start)
	{
		char* r = malloc(*source - start + 1);
		memcpy(r,start,*source - start);
		r[*source - start] = '\0';

		struct Token* keyword = matchKeyword(r,line);

		if(keyword != NULL)
		{
			free(r);
			return keyword;
		}

		return allocateToken(NAME_TOKEN,r,*line);
	}

	return NULL;
}

struct Token* matchStringLiteral(char** source, int* line)
{
	#if DEBUG
	printf("matchStringLiteral()\n");
	#endif

	if(**source != '\'' && **source != '"')
		return NULL;

	char* s = *source;

	do
	{
		(*source)++;
	} while(**source != *s && **source != '\0' && **source != EOF);

	char* r = malloc(*source - s);
	char* p = r;

	while(s < *source)
	{
		*p = *s;
		p++;
		s++;
	}

	*p = '\0';
	(*source)++;
	return allocateToken(STRING_LITERAL_TOKEN,r,*line);
}

// TODO Make this more functional.
struct Token* matchToken(char** source,int* line)
{
	#if DEBUG
	printf("matchToken()\n");
	#endif

	assert(source != NULL);
	assert(*source != NULL);
	assert(line != NULL);
	assert(*line > 0);

	char* s = *source;
	int l = *line;

	ignore(source,line);

	// Source and line can increase but not decrease.
	assert(s <= *source);
	assert(l <= *line);

	s = *source;
	l = *line;

	if(**source == '\0' || **source == EOF)
		return NULL;

	struct Token* (*matchers[])(char**,int*) =
	{
		matchIntegerLiteral,
		matchOperator,
		matchName,
		matchStringLiteral
	};

	struct Token* r;
	int i;

	for(i = 0; i < 4; i++)
	{
		r = matchers[i](source,line);

		assert(l == *line); // Only ignore() should ever change line.

		if(r != NULL)
		{
			assert(s < *source); // Source should advance.
			return r;
		}

		// Source should not advance.
		assert(s == *source);
	}

	return NULL;
}

struct TokenList* matchTokenList(char** source,int* line)
{
	#if DEBUG
	printf("matchTokenList()\n");
	#endif

	assert(source != NULL);
	assert(*source != NULL);
	assert(line != NULL);
	assert(*line > 0);

	struct Token* token = matchToken(source,line);

	if(token == NULL) return NULL;

	#if DEBUG
	printf(
		"Matched token: %s, %s\n",
		tokenTagToString(token->tag),
		token->characters);
	#endif

	return allocateTokenList(token,matchTokenList(source,line));
}

struct TokenList* tokenize(char* source)
{
	assert(source != NULL);

	#if DEBUG
	printf("tokenize()\n");
	#endif

	int line = 1;
	return matchTokenList(&source,&line);
}

#endif
